import { existsSync, readdirSync, statSync } from "fs";
import { dirname, extname, join, resolve } from "path";
import { MDMDocument } from "../metadata";
import { readFileSync } from "../../common/system";
import { isSupportedFile, stringEqual, stringIsFloat, stringIsInteger } from "../../common/util";
import { TypeChecker } from "./checker";
import { createConverter } from "./converter";
import { createNodeFactory } from "./factory";
import { basicTypes } from "./impl";
import { Parser, PreprocessorParser } from "./parser";
import * as t from "./types";
import {
    DefaultOptions,
    GetConfigurationHandler,
    MDMLoaderOptions,
    MDMSerializerOptions,
    Mutable,
    ScriptOptions
} from "../../common/types";

const factory = createNodeFactory();
const converter = createConverter(factory);
const checker = new TypeChecker(converter, DefaultOptions.scriptOptions);

// 载入本地初始定义文件内容，为 globals 文件夹内的内容
const GLOBAL_DEFINITIONS_FOLDER_PATH = resolve(__dirname, "../../../globals");

namespace ProgramLoader {

    /**
     * 读取文件夹内所有支持的文件路径。
     * @param folder 文件夹路径
     * @returns 所有支持的文件路径列表
     */
    export function readSupportedFsPaths(folder: string): string[] {
        let fsPaths: string[] = [];
        readdirSync(folder, { withFileTypes: true }).forEach(file => {
            if (file.isDirectory()) {
                fsPaths.push(...readSupportedFsPaths(join(folder, file.name)));
            } else {
                let lowerPath = file.name.toLowerCase();
                if (lowerPath.endsWith(t.GenerateFileExtens.metadata) || lowerPath.endsWith(t.GenerateFileExtens.routing)) {
                    return;
                }
                else if (isSupportedFile(file.name)) {
                    fsPaths.push(join(folder, file.name));
                }
            }
        });
        return fsPaths;
    }

    const batMrScriptRegex = /\s*(?:mrscriptcl)\s*(?:\".*\"|[a-zA-Z_0-9]*\.mrs)(?:\s*\/d\:([a-zA-Z_0-9]*=(?:(?:\"\\\".*?\\\"\")|(?:[0-9]*)|true|false)))*\s*(?:>>\s*[a-zA-Z_0-9]*\.[a-zA-Z_0-9]*)?/ig;
    const batMrScriptItemRegex = /(?:\s*\/d\:(?:([a-zA-Z_0-9]*)=((?:\"\\\".*?\\\"\")|(?:[0-9]*)|true|false)))/i;
    const batRunDmsScriptRegex = /\s*(?:dmsrun)\s*(?:(?:\"[a-zA-Z_0-9]*\")|(?:[a-zA-Z_0-9]*\.dms))(\s+\/d\s*\"[a-zA-Z_]\w*\s+(?:(?:\\\".*\\\")|(?:[0-9]+)|(?:[a-zA-Z_][\w\.]*))\")*/igm;
    const batRunDmsScriptItemRegex = /(?:\s+\/d\s*\"([a-zA-Z_]\w*)\s+((?:\\\".*\\\")|(?:[0-9]+)|(?:[a-zA-Z_][\w\.]*))\")/i;

    function getBatMacroValueType(value: string): "string" | "boolean" | "number" | "other" {
        if (/true|false/i.test(value)) {
            return "boolean";
        }
        else if (/\"\\\".*?\\\"\"/.test(value)) {
            return "string";
        }
        else if (/[0-9]+/.test(value)) {
            return "number";
        }
        else {
            return "other";
        }
    }

    function readMacros(fileName: string, content: string, globalRegExp: RegExp, itemRegExp: RegExp, map: Map<string, t.BatchMacro>) {
        if (globalRegExp.test(content)) {
            let match;
            while (match = itemRegExp.exec(content)) {
                if (match.length < 3) {
                    continue;
                }
                let name = match[1];
                let value = match[2];
                let type = getBatMacroValueType(value);
                map.set(name.toLowerCase(), {
                    fileName,
                    name,
                    value,
                    type,
                    start: itemRegExp.lastIndex - match[0].length,
                    end: itemRegExp.lastIndex
                });
            }
        }
    }

    /**
     * 读取指定文本内容中所有的宏定义内容。
     * @param path 文件路径
     * @param content 文件文本内容
     * @returns
     */
    export function readBatchMacroFromString(path: string, content: string) {
        let result: Map<string, t.BatchMacro> = new Map();
        readMacros(path, content, batMrScriptRegex, new RegExp(batMrScriptItemRegex.source, "igm"), result);
        readMacros(path, content, batRunDmsScriptRegex, new RegExp(batRunDmsScriptItemRegex.source, "igm"), result);
        return result;
    }

    let dfsStack: t.FileReference[] = [];

    function dfs(file: t.File, callback: (f: t.File) => void) {
        if (file.tracker.parents.length === 0) {
            return;
        }
        for (const p of file.tracker.parents) {
            dfsStack.push(p);
            callback(p.file);
            dfs(p.file, callback);
            dfsStack.pop();
        }
    }

    /**
     * 获取指定文件所在的文件引用路径中最短的一个。
     * 如果当前文件没有父节点，则视为入口文件。
     * @param file 当前文件
     * @returns
     */
    export function getLowestDepthPath(file: t.File): t.FileReference[] {
        dfsStack.splice(0);
        // 如果没有父节点，返回传入节点
        if (file.tracker.parents.length === 0) {
            return [{ file, commentReference: false, span: { start: 0, length: 0 } }];
        }

        let result: t.FileReference[] = [];
        dfs(file, f => {
            if (f.tracker.parents.length === 0 && (result.length === 0 || result.length > dfsStack.length)) {
                result = dfsStack.slice(0);
            }
        });
        return result;
    }

}

function forEachSourceFileInclude(source: t.SourceFile, callback: (s: t.SourceFile) => void, thisArg?: any) {
    let func = thisArg ? callback.bind(thisArg) : callback;
    for (const s of source.references) {
        func(s);
        forEachSourceFileInclude(s, callback, thisArg);
    }
}

function forEachFileChild(file: t.File, stack: t.File[], callback: (f: t.FileReference) => void, thisArg?: any) {
    let func = thisArg ? callback.bind(thisArg) : callback;
    for (const child of file.tracker.children) {
        stack.push(child.file);
        func(child);
        forEachFileChild(child.file, stack, callback, thisArg);
        stack.pop();
    }
}

function forEachFileParent(file: t.File, stack: t.File[], callback: (f: t.FileReference) => void, thisArg?: any) {
    let func = thisArg ? callback.bind(thisArg) : callback;
    for (const parent of file.tracker.parents) {
        stack.push(parent.file);
        func(parent);
        forEachFileParent(parent.file, stack, callback, thisArg);
        stack.pop();
    }
}

function buildFileTree(entries: t.File[]) {
    for (const entry of entries) {
        entry.tracker.asBranch(entry, 1);
        if (!entry.isLeaf()) {
            let stack: t.File[] = [ entry ];
            entry.traverse(e => {
                e.file.tracker.asBranch(entry, stack.length);
            }, stack);
        }
    }
}

export class FileTracker implements t.FileTracker {

    public readonly file: t.File;

    private _parents: t.FileReference[];
    private _children: t.FileReference[];
    private _branches: Map<string, t.FileBranch>;
    private _lockedBranch?: t.FileBranch;
    private _latest?: t.SourceFile;

    public constructor(file: t.File) {
        this.file = file;
        this._parents = [];
        this._children = [];
        this._branches = new Map();
    }

    public get parents(): readonly t.FileReference[] {
        return this._parents.slice();
    }

    public get children(): readonly t.FileReference[] {
        return this._children.slice();
    }

    public get branches(): readonly t.FileBranch[] {
        return Array.from(this._branches.values()).sort((a, b) => a.depth - b.depth);
    }

    private asMember(fileRef: t.FileReference, collection: t.FileReference[]) {
        let obj = collection.find(e => e.file === fileRef.file);
        if (obj) {
            (obj as Mutable<t.FileReference>).directive = fileRef.directive;
            (obj as Mutable<t.FileReference>).commentReference = fileRef.commentReference;
        }
        else {
            collection.push(fileRef);
        }
    }

    public asParent(file: t.File, mark: t.FileDirective | undefined, commentReference: boolean, position: t.TextSpan): void {
        this.asMember({ file, directive: mark, commentReference, span: position }, this._parents);
    }

    public asChild(file: t.File, mark: t.FileDirective | undefined, commentReference: boolean, position: t.TextSpan): void {
        this.asMember({ file, directive: mark, commentReference, span: position }, this._children);
    }

    public getParent(path: string): t.FileReference | undefined {
        return this._parents.find(e => stringEqual(e.file.path, path));
    }

    public getChild(path: string): t.FileReference | undefined {
        return this._children.find(e => stringEqual(e.file.path, path));
    }

    public isParent(file: t.File): boolean {
        return this._parents.find(e => e.file === file) !== undefined;
    }

    public isChild(file: t.File): boolean {
        return this._children.find(e => e.file === file) !== undefined;
    }

    private removeMember(arg: string | t.File | ((file: t.File) => boolean), collection: t.FileReference[]): t.FileReference | undefined {
        let findIndex: number;
        if (typeof arg === "string") {
            findIndex = collection.findIndex(e => stringEqual(e.file.path, arg));
        }
        else if (typeof arg === "function") {
            findIndex = collection.findIndex(e => arg(e.file));
        }
        else {
            findIndex = collection.findIndex(e => e.file === arg);
        }
        if (findIndex > -1) {
            let removed = collection[findIndex];
            collection.splice(findIndex, 1);
            return removed;
        }
    }

    public removeParent(path: string): void;
    public removeParent(file: t.File): void;
    public removeParent(func: (file: t.File) => boolean): void;
    public removeParent(arg: string | t.File | ((file: t.File) => boolean)) {
        let removed = this.removeMember(arg, this._parents);
        if (removed && removed.file.isEntry()) {
            this._branches.delete(removed.file.path.toLowerCase());
        }
    }

    public clearParents(): void {
        this._parents.splice(0);
    }

    public removeChild(path: string): void;
    public removeChild(file: t.File): void;
    public removeChild(func: (file: t.File) => boolean): void;
    public removeChild(arg: string | t.File | ((file: t.File) => boolean)) {
        this.removeMember(arg, this._children);
    }

    public clearChildren(): void {
        this._children.splice(0);
    }

    public lockBranch(entryPath: string): t.FileBranch | undefined {
        let branch = this._branches.get(entryPath.toLowerCase());
        if (branch) {
            this._lockedBranch = branch;
            if (!branch.source) {
                this.file.getProgram().parse(branch);
            }
        }
        return branch;
    }

    public asBranch(entry: t.File, depth: number): void {
        this._branches.set(entry.path.toLowerCase(), {
            entry,
            depth,
            source: undefined
        });
    }

    public getEntry(): t.File;
    public getEntry(path: string): t.File | undefined;
    public getEntry(path?: string): t.File | undefined {
        if (!path) {
            return this._branches.size === 0 ? this.file : this.getBranch().entry;
        }
        return this._branches.get(path.toLowerCase())?.entry;
    }

    public getBranch(): t.FileBranch;
    public getBranch(path: string): t.FileBranch | undefined;
    public getBranch(path?: string): t.FileBranch | undefined {
        if (!path) {
            return this._lockedBranch ?? this.branches[0];
        }
        return this._branches.get(path.toLowerCase());
    }

    public updateBranch(source: t.SourceFile, entryPath?: string): void {
        let branch: t.FileBranch | undefined;
        if (entryPath) {
            branch = this.getBranch(entryPath);
        }
        else {
            branch = this.getBranch();
        }
        if (branch) {
            (branch as Mutable<t.FileBranch>).source = source;
        }
        this._latest = source;
    }

    public getSourceFile(entryPath?: string): t.SourceFile | undefined {
        if (!entryPath) {
            return this._lockedBranch ? this._lockedBranch.source : (this._latest ?? (this.branches.length > 0 ? this.branches[0].source : undefined));
        }
        return this.getBranch(entryPath)?.source;
    }

}

export class File implements t.File {

    private _content: string;
    private _initialized: boolean;

    public readonly kind: t.FileKind;
    public readonly tracker: t.FileTracker;
    public readonly directives: t.FileDirective[];
    public readonly version: number;

    public constructor(
        public readonly path: string,
        content: string,
        private readonly _program: Program
    ) {
        this.directives = [];
        this._content = content;
        this._initialized = false;

        this.tracker = new FileTracker(this);

        this.kind = t.FileKind.none;
        const lowerPath = path.toLowerCase();
        if (lowerPath.endsWith(t.GenerateFileExtens.declare)) {
            this.kind = t.FileKind.declare;
        }
        else if (lowerPath.endsWith(t.GenerateFileExtens.metadata)) {
            this.kind = t.FileKind.mdd;
        }
        else if (lowerPath.length > 4) {
            switch (lowerPath.substring(lowerPath.length - 4)) {
                case ".mrs":
                    this.kind = t.FileKind.mrs;
                    break;
                case ".dms":
                    this.kind = t.FileKind.dms;
                    break;
                case ".ini":
                    this.kind = t.FileKind.ini;
                    break;
                case ".bat":
                    this.kind = t.FileKind.bat;
                    break;
                case ".mdd":
                    this.kind = t.FileKind.mdd;
                    break;
            }
        }

        this.version = 0;
    }

    public get content() {
        return this._content;
    }

    public set content(value: string) {
        this._content = value;
    }

    public init() {
        if (this._initialized) {
            return;
        }
        this.clearParent();
        this.clearChildren();
        this.updateFileReference();
        this._initialized = true;
    }

    public getProgram(): t.Program {
        return this._program;
    }

    private clearReferenceParent() {
        if (!this._program.initialized) {
            return;
        }
        // 清空已有的使用注释标记的父子对象关系
        for (const ref of this.directives) {
            this.tracker.removeChild(ref.fileName);
        }
        this.directives.splice(0);
    }

    private clearParent() {
        if (!this._program.initialized) {
            return;
        }
        this.clearReferenceParent();
        // 清空已有的父子对象关系
        for (const parent of this.tracker.parents) {
            parent.file.tracker.removeChild(this);
        }
        this.tracker.clearParents();
    }

    private clearChildren() {
        for (const child of this.tracker.children) {
            child.file.tracker.removeParent(this);
        }
        this.tracker.clearChildren();
    }

    private updateFileReference(startPos?: number) {
        let parseResult = PreprocessorParser.parse(this.path, this._content, startPos);
        if (startPos === 0 || startPos === undefined) {
            for (const reference of parseResult.commentReferences) {
                const resolvedPath = resolve(dirname(this.path), reference.fileName);
                this.directives.push({
                    fileName: resolvedPath,
                    value: reference.value,
                    span: reference.span
                });
                const existFile = this._program.getFile(resolvedPath);
                if (existFile) {
                    if (!reference.value) {
                        this.tracker.asChild(existFile, reference, true, reference.span);
                        existFile.tracker.asParent(this, undefined, true, reference.span);
                    }
                    else {
                        this.tracker.asParent(existFile, reference, true, reference.span);
                        existFile.tracker.asChild(this, undefined, true, reference.span);
                    }
                }
            }
        }
        for (const node of parseResult.nodes) {
            const existFile = this._program.getFile(node.resolvedPath);
            if (existFile) {
                const span: t.TextSpan = {
                    start: node.start,
                    length: node.end - node.start
                };
                this.tracker.asChild(existFile, undefined, false, span);
                existFile.tracker.asParent(this, undefined, false, span);
            }
        }
    }

    public update(param: t.FileContentUpdateParam) {
        this._content = param.text;
        this.clearReferenceParent();
        this.clearChildren();
        this.updateFileReference();
        // 更新入口文件
        let stack: t.File[] = [];
        forEachFileParent(this, stack, parent => {
            if (parent.file.isEntry()) {
                this.tracker.asBranch(parent.file, stack.length);
            }
        });
    }

    public isEntry(): boolean {
        return this.tracker.parents.length === 0;
    }

    public isLeaf(): boolean {
        return this.tracker.children.length === 0;
    }

    public getParent(predicate: (file: t.File) => boolean): t.File | undefined {
        let result: t.File | undefined;
        for (const { file } of this.tracker.parents) {
            if (predicate(file)) {
                return file;
            }
            result = file.getParent(predicate);
        }
        return result;
    }

    public getSourceFile(entryPath: string | undefined): t.SourceFile | undefined {
        if (!entryPath) {
            return this.tracker.getSourceFile();
        }
        return this.tracker.getSourceFile(entryPath);
    }

    public updateSourceFile(sourceFile: t.SourceFile, entryPath?: string) {
        this.tracker.updateBranch(sourceFile, entryPath);
    }

    public traverse(callback: (node: t.FileReference) => void, stack?: t.File[]) {
        forEachFileChild(this, stack ?? [], callback, this);
    }

}

export class Program implements t.Program {

    private _log?: t.Logger;
    private _rootPath: string;
    private _map: Map<string, t.File>; // key - lower fsPath
    private _declareFiles: t.File[]; // declare files - .d.mrs
    private _scriptFiles: t.File[];  // script files - .mrs .dms
    private _batchFiles: t.File[];   // batch files - .bat

    private _initialized: boolean;
    private _loading: boolean;
    private _parsing: boolean;

    private _localDeclarations?: Map<string, t.Symbol>;
    private _localMetadata?: string;
    private _localMetadataScope?: Map<string, t.Symbol>;
    private _batchMacros: Map<string, t.BatchMacro>;

    private _activeFiles: Map<string, t.File>;

    private _scriptOptions: ScriptOptions;

    private _showErrorMessageHandler?: t.Logger;
    private _getMdmSerializerOptionHandler?: GetConfigurationHandler<MDMSerializerOptions>;
    private _getMdmLoaderOtionHandler?: GetConfigurationHandler<MDMLoaderOptions>;

    private _fileChangedHandler?: t.ProgramFileChangedHandler;

    constructor() {
        this._rootPath = "";
        this._map = new Map();
        this._initialized = false;
        this._loading = false;
        this._parsing = false;
        this._declareFiles = [];
        this._scriptFiles = [];
        this._batchFiles = [];
        this._batchMacros = new Map();
        this._activeFiles = new Map();
        // default options
        this._scriptOptions = Object.assign({}, DefaultOptions.scriptOptions);
    }

    public get initialized() {
        return this._initialized;
    }

    public get localMetadata() {
        return this._localMetadata;
    }

    public clear(): void {
        this._rootPath = "";
        this._map.clear();
        this._loading = false;
        this._declareFiles.splice(0);
        this._scriptFiles.splice(0);
        this._batchFiles.splice(0);
        this._batchMacros.clear();
        this._localDeclarations = undefined;
        this._localMetadataScope = undefined;
    }

    public onGetMdmSerializerOption(handler: GetConfigurationHandler<MDMSerializerOptions>) {
        this._getMdmSerializerOptionHandler = handler;
    }

    public onGetMdmLoaderOption(handler: GetConfigurationHandler<MDMLoaderOptions>) {
        this._getMdmLoaderOtionHandler = handler;
    }

    public onError(handler: t.Logger): void {
        this._showErrorMessageHandler = handler;
    }

    private getMdmSerializerOption() {
        if (this._getMdmSerializerOptionHandler) {
            return this._getMdmSerializerOptionHandler(this._rootPath);
        }
    }

    private getMdmLoaderOption() {
        if (this._getMdmLoaderOtionHandler) {
            return this._getMdmLoaderOtionHandler(this._rootPath);
        }
    }

    private showErrorMessage(message: string) {
        if (this._showErrorMessageHandler) {
            this._showErrorMessageHandler(message);
        }
    }

    private log(message: string) {
        if (this._log) {
            this._log(message);
        }
    }

    public onProgramFileChanged(handler: t.ProgramFileChangedHandler) {
        this._fileChangedHandler = handler;
    }

    private fileChanged(file: t.File) {
        if (this._fileChangedHandler) {
            this._fileChangedHandler(file);
        }
    }

    public getCurrentDirectory(): string {
        return this._rootPath;
    }

    public updateScriptOptions(options: ScriptOptions): void {
        this._scriptOptions = Object.assign({}, options);
    }

    public onLog(handler?: t.Logger | undefined): void {
        this._log = handler;
    }

    private loadFile(path: string, initByLoader = false): t.File {
        const file = new File(path, readFileSync(path), this);
        if (!initByLoader) {
            file.tracker.asBranch(file, 0);
        }
        return file;
    }

    private updateFileMap() {
        let supportedPaths = ProgramLoader.readSupportedFsPaths(this._rootPath);
        let files = supportedPaths.map(path => this.loadFile(path, true));
        for (const f of files) {
            this._map.set(f.path.toLowerCase(), f);
        }
        let entries: t.File[] = [];
        // 读取声明文件和批处理宏名
        for (const [p, f] of this._map) {
            if (f.kind === t.FileKind.declare) {
                this._declareFiles.push(f);
                entries.push(f);
            }
            else if (f.kind === t.FileKind.bat) {
                this._batchFiles.push(f);
                let macros = ProgramLoader.readBatchMacroFromString(p, f.content);
                for (const [k, v] of macros) {
                    this._batchMacros.set(k, v);
                }
            }
            else {
                f.init();
                this._scriptFiles.push(f);
                if (f.isEntry()) {
                    entries.push(f);
                }
            }
        }
        // 从入口文件开始，构建文件树
        buildFileTree(entries);
    }

    public load(path: string): void {
        if (!existsSync(path)) {
            this.log(`文件 (${path}) 不存在.`);
            return;
        }
        this._loading = true;
        this._rootPath = path;
        this.log(`读取项目 (${this._rootPath}).`);
        // 检查路径是文件还是文件夹，如果是文件的话，检查是否是元数据文件
        let pathStat = statSync(path);
        if (pathStat.isFile()) {
            this._map.set(path.toLowerCase(), this.loadFile(path));
            this.log(`项目 (${path}) 载入完成.`);
            this._loading = false;
            this._initialized = true;
            return;
        }
        this.updateFileMap();
        this.log(`项目 (${this._rootPath}) 载入完成.`);
        // 首先判断本地声明文件是否已经解析，如果未解析，需要前置解析，并保存本地符号表
        if (this._declareFiles.length > 0) {
            let localDeclares = Parser.parseDeclareScripts(this._declareFiles);
            this._localDeclarations = checker.checkDeclareFiles(localDeclares) as Map<string, t.Symbol>;
            // 更新File的SourceFile对象
            for (const source of localDeclares) {
                let file = this._map.get(source.fileName.toLowerCase());
                if (file) {
                    file.updateSourceFile(source);
                }
            }
        }
        this._loading = false;
        this._initialized = true;
    }

    public loadLocalMetadataFile(path: string): void {
        if (!existsSync(path) || !statSync(path).isFile()) {
            return;
        }
        let doc = new MDMDocument();
        let errors = doc.open(path);
        if (errors.length > 0) {
            this.showErrorMessage(`错误: ${errors.map(e => e.message).join(", ")}`);
        }
        let exportFile = doc.exportSerializedFile(this.getMdmSerializerOption());
        let source = Parser.parse(exportFile.scriptFile, readFileSync(exportFile.scriptFile));
        // 忽略可能存在的错误
        this._localMetadataScope = checker.checkSourceFile(source).table(t.SymbolScope.metadata) as Map<string, t.Symbol>;
        this._localMetadata = path;
        this.refresh();
    }

    public addFile(fileName: string, content?: string | undefined): void {
        if (!content) {
            content = readFileSync(fileName);
        }
        let file = this._map.get(fileName.toLowerCase());
        if (file) {
            file.update({ text: content, changes: [] });
        }
        else {
            file = new File(fileName, content, this);
            file.init();
            // 更新入口文件
            if (file.isEntry()) {
                file.tracker.asBranch(file, 0);
                let stack: t.File[] = [];
                forEachFileChild(file, stack, ref => {
                    ref.file.tracker.asBranch(file!, stack.length);
                });
            }
            else {
                let stack: t.File[] = [];
                forEachFileParent(file, stack, parent => {
                    if (parent.file.isEntry()) {
                        file?.tracker.asBranch(parent.file, stack.length);
                    }
                });
            }
            this._map.set(fileName.toLowerCase(), file);
        }
    }

    public deleteFile(fileName: string): void {
        let lowerName = fileName.toLowerCase();
        let exist = this._map.get(fileName);
        if (exist) {
            for (const parent of exist.tracker.parents) {
                parent.file.tracker.removeChild(exist);
            }
            for (const child of exist.tracker.children) {
                child.file.tracker.removeParent(exist);
            }
            this._map.delete(lowerName);
        }
    }

    public renameFile(oldName: string, newName: string): void {
        let exist = this._map.get(oldName.toLowerCase());
        if (exist) {
            (exist as Mutable<t.File>).path = newName;
            for (const branch of exist.tracker.branches) {
                if (branch.source) {
                    (branch.source as Mutable<t.SourceFile>).fileName = newName;
                }
            }
        }
    }

    public activateFile(path: string) {
        let lowerPath = path.toLowerCase();
        let file = this._map.get(lowerPath);
        if (file) {
            (file as Mutable<t.File>).version++;
            this._activeFiles.set(lowerPath, file);
        }
    }

    public closeFile(path: string) {
        let lowerPath = path.toLowerCase();
        let exist = this._activeFiles.get(lowerPath);
        if (exist) {
            (exist as Mutable<t.File>).version--;
        }
        this._activeFiles.delete(lowerPath);
    }

    public refresh() {
        if (this._activeFiles.size === 0) {
            return;
        }
        // 对`version`值不同的活动文件进行分类，并对各组分别进行更新
        let group = new Map<number, t.File[]>();
        for (const [, f] of this._activeFiles) {
            let version = f.version;
            if (!group.has(version)) {
                group.set(version, [ f ]);
            }
            else {
                group.get(version)?.push(f);
            }
        }

        for (const [v, g] of group) {
            // 遍历所有活动文件，因为更新文件内容时，`File.version`会递增，并且所有通过#include
            // 引入的文件会同步递增，如果出现相关联的文件时，其`File.version`会和开始的值不同。
            for (const file of g) {
                if (file.version === v) {
                    this.parse(file.path);
                }
            }
        }
    }

    public parse(fileName: string): t.SourceFile | undefined;
    public parse(branch: t.FileBranch): t.SourceFile | undefined;
    public parse(arg: string | t.FileBranch): t.SourceFile | undefined {
        // 如果还在解析的进程中，直接返回
        if (this._parsing) {
            return;
        }
        // 如果正在读取，等待读取结束
        if (this._loading) {
            while (this._loading) {
                setTimeout(resolve, 200);
            }
        }
        let entry: t.File;
        if (typeof arg === "string") {
            // 查询文件是否在列表中
            let lowerName = arg.toLowerCase();
            let exist = this._map.get(lowerName);
            if (!exist) {
                this.log(`文件 (${arg}) 不存在.`);
                return;
            }
            // 查找入口文件
            entry = exist.tracker.getEntry();
        }
        else {
            entry = arg.entry;
        }
        Parser.onResolveIncludeIdentifier(text => {
            let markedFile = entry.tracker.children.find(e => {
                return e.file.directives.find(ref => ref.value && stringEqual(text, ref.value));
            });
            return markedFile ? markedFile.file.path : "";
        });
        // 如果还是没有找到，视为解析失败
        if (!entry) {
            this.log(`查找入口文件失败. 源文件路径为: ${arg}`);
            return;
        }

        this._parsing = true;
        Parser.onSearchProgramFile(this.getFile.bind(this));
        try {
            const source = Parser.parse(entry.path, entry.content);
            // 如果有本地定义，向 Checker 加入本地定义
            checker.setLocalDeclarations(this._localDeclarations);
            checker.setLocalMetadatas(this._localMetadataScope);
            // 声明Batch文件声明的宏
            if (this._batchMacros.size > 0) {
                for (const [, macro] of this._batchMacros) {
                    const symbol = checker.createSymbol(
                        t.SymbolKind.macroVariable,
                        macro.name,
                        t.CheckFlag.readonly,
                        converter.convertNodeToDeclaration(factory.createIdentifier(macro.name)),
                        macro.type === "boolean" ?
                            basicTypes.boolean :
                            macro.type === "number" && stringIsFloat(macro.value) ?
                            basicTypes.double :
                            macro.type === "number" && stringIsInteger(macro.value) ?
                            basicTypes.long :
                            macro.type === "string" ?
                            basicTypes.string :
                            basicTypes.any
                    );
                    symbol.set("initialization", macro.value);
                    source.locals.set(symbol, t.SymbolScope.macro);
                }
            }
            checker.updateOptions(this._scriptOptions);
            checker.checkSourceFile(source);
            // 更新文件对象的入口点和源文件对象
            this.updateFileProperty(entry, source, entry);
            this.updateSourceFile(source, entry);

            return source;
        }
        catch (error) {
            if (error instanceof Error) {
                this.log(`错误: ${error.message}`);
            }
            else {
                throw error;
            }
        }
        finally
        {
            // 清空Checker和Parser中针对具体文件的配置。
            checker.setLocalDeclarations();
            checker.setLocalMetadatas();
            checker.clear();
            Parser.onSearchProgramFile();
            Parser.onResolveIncludeIdentifier();
            this._parsing = false;
        }
    }

    private updateFileProperty(file: t.File, source: t.SourceFile, entry?: t.File) {
        file.updateSourceFile(source, entry?.path);
        this.fileChanged(file);
    }

    private updateFile(source: t.SourceFile, entry?: t.File) {
        let file = this._map.get(source.fileName.toLowerCase());
        if (file) {
            (file as Mutable<t.File>).version++;
            this.updateFileProperty(file, source, entry);
        }
    }

    private updateSourceFile(source: t.SourceFile, entry?: t.File) {
        // 更新Program级别内直接include的文件
        forEachSourceFileInclude(source, s => this.updateFile(s, entry), this);
        // 更新Section内的引入的文件
        for (const node of source.sections) {
            if (!node.isStatement<t.EventSection>(t.SyntaxKind.eventSection) &&
                !node.isStatement<t.MetadataSection>(t.SyntaxKind.metadataSection)) {
                continue;
            }
            for (const include of node.includes) {
                if (include.targetFile) {
                    this.updateFile(include.targetFile, entry);
                    forEachSourceFileInclude(include.targetFile, s => this.updateFile(s, entry), this);
                }
            }
        }
    }

    public update(fileName: string, param: t.FileContentUpdateParam): void {
        const lowerName = fileName.toLowerCase();
        if (lowerName.startsWith(GLOBAL_DEFINITIONS_FOLDER_PATH.toLowerCase())) {
            return;
        }
        let file = this._map.get(lowerName);
        if (file) {
            file.update(param);
        }
        else {
            this.addFile(fileName, param.text);
        }
    }

    public fileExists(fileName: string): boolean {
        return this._map.has(fileName.toLowerCase());
    }

    public getFile(fileName: string): t.File | undefined {
        return this._map.get(fileName.toLowerCase());
    }

    public getSourceFile(fileName: string, entryPath?: string): t.SourceFile | undefined {
        return this._map.get(fileName.toLowerCase())?.getSourceFile(entryPath);
    }

    public getTypeChecker(): t.TypeChecker {
        return checker;
    }

    public getLocalDeclarations(): ReadonlyMap<string, t.Symbol> | undefined {
        return this._localDeclarations;
    }

    public getRelatives<T = string>(fileName: string, callback?: (file: t.File) => T | undefined): T[] {
        let fn: (file: t.File) => T | undefined = callback || function (file) {
            return file.path as unknown as T | undefined;
        };
        let result: T[] = [];
        let file = this.getFile(fileName);
        let branch = file?.tracker.getBranch();
        if (file && branch) {
            this._activeFiles.forEach(f => {
                if (f.tracker.getBranch().entry === branch!.entry) {
                    let item = fn(f);
                    if (item) {
                        result.push(item);
                    }
                }
            });
        }
        return result;
    }

}

// 本地声明文件的Program对象
const GLOBAL_DEFINITIONS_PROGRAM = new Program();
GLOBAL_DEFINITIONS_PROGRAM.load(GLOBAL_DEFINITIONS_FOLDER_PATH);
// 由 Checker 绑定类型
let globalScope = GLOBAL_DEFINITIONS_PROGRAM.getLocalDeclarations()!;

export interface GlobalLibrary {
    iom: t.Symbol;
    tom: t.Symbol;
    mdm: t.Symbol;
    qom: t.Symbol;
    dom: t.Symbol;
    core: t.Symbol;
}

const globalLibs: GlobalLibrary = {
    iom: globalScope.get("IOM".toLowerCase())!,
    tom: globalScope.get("TOM".toLowerCase())!,
    mdm: globalScope.get("MDM".toLowerCase())!,
    qom: globalScope.get("QOM".toLowerCase())!,
    dom: globalScope.get("DOM".toLowerCase())!,
    core: globalScope.get("MRSCRIPTCORELib".toLowerCase())!,
};

checker.setGlobalDeclarations(globalScope);

export async function validateSingleSourceFile(path: string, content: string, fileFlag: t.FileKind = t.FileKind.mrs) {
    let source = Parser.parse(path, content, 0, fileFlag);
    checker.checkSourceFile(source);
    return source;
}

export function getGlobalDefinitionPath(): string {
    return GLOBAL_DEFINITIONS_FOLDER_PATH;
}

export function getGlobalDefinitionProgram(): t.Program {
    return GLOBAL_DEFINITIONS_PROGRAM;
}

export function getGlobalScope(): ReadonlyMap<string, t.Symbol> {
    return globalScope;
}

export function getGlobalLib(name: keyof GlobalLibrary): t.Symbol {
    return globalLibs[name];
}

export function getTypeChecker(): t.TypeChecker {
    return checker;
}
